#include "StdAfx.h"
#include <WinInet.h>
#include "DesktopSorter.h"
#include "..\FilerFrogUtils\FilerFrogUtils.h"
#include "Dialogs\FrogMessageBox.h"
#include <algorithm>
#include "Shlwapi.h"

#pragma warning(disable:4482)

DesktopSorter::DesktopSorter(IFolderView* pFolderView):
m_pFolderView(pFolderView),
m_TextFont(NULL),
m_TextBrush(Color::White),
m_TextShadowBrush(Color::MakeARGB(200, 0, 0, 0)),
m_BroderPen(Color::White, 4),
m_SilentMode(false)
{
	GdiplusStartup(&m_GdiPlusToken, &m_GdiPlusStartupInput, NULL);
	//m_TextFont = new Gdiplus::Font(L"Times new Roman", 20, FontStyle::FontStyleRegular);

	HDC hDDC = GetDC(GetDesktopWindow());
	m_TextFont = new Gdiplus::Font(hDDC, &m_DesktopSortConfig.groupNamefont);
}

DesktopSorter::~DesktopSorter()
{
	if (m_TextFont)
	{
		delete m_TextFont;
		m_TextFont = NULL;
	}

	if (m_GdiPlusToken)
	{
		GdiplusShutdown(m_GdiPlusToken);
	}
}

void DesktopSorter::AddDesktopItem(LPCITEMIDLIST itemIdList, DesktopItemType itemType, wstring filePath)
{
	DesktopItem desktopItem;
	desktopItem.itemIdList = itemIdList;
	desktopItem.itemType = itemType;
	desktopItem.filePath = filePath;

	if (m_pFolderView)
	{
		m_pFolderView->GetItemPosition(itemIdList, &desktopItem.originalPosition);
	}

	m_DesktopItems.push_back(desktopItem);
}

int DesktopSorter::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
	UINT num = 0;          // number of image encoders
	UINT size = 0;         // size of the image encoder array in bytes

	ImageCodecInfo* pImageCodecInfo = NULL;

	GetImageEncodersSize(&num, &size);

	if (size == 0)
	{
		return -1;  // Failure
	}

	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));

	if (pImageCodecInfo == NULL)
	{
		return -1;  // Failure
	}

	GetImageEncoders(num, size, pImageCodecInfo);

	for (UINT j = 0; j < num; ++j)
	{
		if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;  // Success
		}    
	}

	free(pImageCodecInfo);
	return -1;  // Failure
}

struct DesktopItemSort
{
	bool operator()(DesktopItem& item1, DesktopItem& item2)
	{
		wstring name1 = item1.filePath;
		wstring name2 = item2.filePath;

		if (item1.itemType == DesktopItemType::SystemFolder)
		{
			name1 = FilerFrogUtils::GetFileNameFromPath(item1.filePath);
		}

		if (item2.itemType == DesktopItemType::SystemFolder)
		{
			name2 = FilerFrogUtils::GetFileNameFromPath(item2.filePath);
		}

		return name1 < name2;
	}
};

void DesktopSorter::CheckAutoArrange(HWND hwnd)
{
	UINT idAutoArrange = 28785;

	// XP
	if (FilerFrogUtils::IsVistaOs() == false)
	{
		idAutoArrange = 28753;
	}						

	::SendMessage(hwnd, WM_COMMAND, idAutoArrange, 0);
}

void DesktopSorter::CheckSnapToGrid(HWND hwnd)
{
	UINT idAlignToGrid = 28788;

	// XP
	if (FilerFrogUtils::IsVistaOs() == false)
	{
		idAlignToGrid = 28756;
	}						

	::SendMessage(hwnd, WM_COMMAND, idAlignToGrid, 0);	
}

DesktopWallpaperStyle DesktopSorter::GetWallpaperSettings()
{	
	DesktopWallpaperStyle retVal = DesktopWallpaperStyle::BackgroundCenter;

	CRegKey key;

	long nError = key.Open(HKEY_CURRENT_USER, L"Control Panel\\Desktop", KEY_READ);

	if (nError == ERROR_SUCCESS)
	{
		// Code here to read values from MyProgram key
		wchar_t sStyle[1024 + 1];
		wchar_t sTile[1024 + 1];
		ULONG charCount = 1024;

		nError = key.QueryStringValue(L"WallpaperStyle", sStyle, &charCount);
		nError = key.QueryStringValue(L"TileWallpaper", sTile, &charCount);

		wstring wallpaperTiled = sStyle;
		wstring wallpaperStyle = sTile;

		if (wallpaperTiled == L"1" && wallpaperStyle == L"1")
		{
			retVal = DesktopWallpaperStyle::BackgroundTile;
		}
		else if (wallpaperTiled == L"0" && wallpaperStyle == L"0")
		{
			retVal = DesktopWallpaperStyle::BackgroundCenter;
		}
		else if (wallpaperTiled == L"0" && wallpaperStyle == L"2")
		{
			retVal = DesktopWallpaperStyle::BackgroundStretch;
		}
	}

	return retVal;
}

void DesktopSorter::SetWallpaperSettings(const DesktopWallpaperStyle style)
{	
	CRegKey key;

	long nError = key.Open(HKEY_CURRENT_USER, L"Control Panel\\Desktop", KEY_WRITE);

	if (nError == ERROR_SUCCESS)
	{
		wstring wallpaperStyle = L"0";
		wstring wallpaperTiled = L"0";

		switch (style)
		{
		case DesktopWallpaperStyle::BackgroundTile:
			{
				wallpaperStyle = L"1";
				wallpaperTiled = L"1";
			}
			break;
		case DesktopWallpaperStyle::BackgroundCenter:
			{
				wallpaperStyle = L"0";
				wallpaperTiled = L"0";
			}
			break;
		case DesktopWallpaperStyle::BackgroundStretch:
			{
				wallpaperStyle = L"2";
				wallpaperTiled = L"0";
			}
			break;
		}

		key.SetStringValue(L"WallpaperStyle", wallpaperStyle.c_str());
		key.SetStringValue(L"TileWallpaper", wallpaperTiled.c_str());
	}
}

bool DesktopSorter::ArrangeDesktop(const DesktopSortConfig& desktopSortConfig)
{
	m_DesktopSortConfig = desktopSortConfig;

	if (m_TextFont)
	{
		delete m_TextFont;
		m_TextFont = NULL;
	}

	HDC hDDC = GetDC(GetDesktopWindow());
	m_TextFont = new Gdiplus::Font(hDDC, &m_DesktopSortConfig.groupNamefont);

	m_TextBrush.SetColor(m_DesktopSortConfig.fontColor);

	Gdiplus::Color borderColor(m_DesktopSortConfig.groupBorderOpacity,
		m_DesktopSortConfig.borderColor.GetR(),
		m_DesktopSortConfig.borderColor.GetG(),
		m_DesktopSortConfig.borderColor.GetB());

	m_BroderPen.SetColor(borderColor);

	size_t itemsCount = m_DesktopItems.size();

	m_IsAutoArrange = true;
	m_IsSnapToGrid = true;
	m_OriginalDesktop =	GetWallpaperPath();

	DesktopWallpaperStyle wallpaperStyle = GetWallpaperSettings();

	HWND explorerHwnd = NULL;

	if (itemsCount > 0)
	{
		if (m_pFolderView)
		{
			HWND desktopWnd = GetDesktopWindow();
			HWND wnd = FindWindowEx(desktopWnd, 0, L"Progman", NULL);

			// Xp Hacks
			if (wnd == NULL)
			{
				wnd = FindWindowEx(desktopWnd, 0, L"ExploreWClass", NULL);

				if (wnd == NULL)
				{
					wnd = FindWindowEx(desktopWnd, 0, L"CabinetWClass", NULL);
				}
			}

			if (wnd != NULL)
			{				
				wnd = FindWindowEx(wnd, 0, L"SHELLDLL_DefView", NULL);

				if (wnd != NULL)
				{
					explorerHwnd = wnd;

					// Remove Auto Arrange
					if (m_pFolderView->GetAutoArrange() == S_OK)
					{
						m_IsAutoArrange = true;
						// MessageBox(NULL, L"AutoArrange", L"AutoArrange", 0);

						CheckAutoArrange(wnd);
					}
					else
					{
						m_IsAutoArrange = false;
					}

					// Remove Align to Grid
					HWND listWnd = FindWindowEx(wnd, 0, L"SysListView32", NULL);

					DWORD listExStyle = ListView_GetExtendedListViewStyle(listWnd);

					if (listExStyle & LVS_EX_SNAPTOGRID)
					{
						m_IsSnapToGrid = true;
						// MessageBox(NULL, L"SnapToGrid", L"SnapToGrid", 0);

						CheckSnapToGrid(wnd);
					}
					else
					{
						m_IsSnapToGrid = false;
					}

					// Set window Icons size to Normal (Vista Only)
					if (FilerFrogUtils::IsVistaOs())
					{
						UINT idSmallIcons = 28752;
						::SendMessage(wnd, WM_COMMAND, idSmallIcons, 0);
					}
				}
			}
		}

		vector<DesktopItem> systemItems;
		vector<DesktopItem> folderItems;
		vector<DesktopItem> shortcutItems;
		vector<DesktopItem> filesItems;

		for (int i=0; i<itemsCount; i++)
		{
			switch (m_DesktopItems[i].itemType)
			{
			case DesktopItemType::Folder:
				{
					folderItems.push_back(m_DesktopItems[i]);
				}
				break;
			case DesktopItemType::System:
			case DesktopItemType::SystemFolder:
				{
					systemItems.push_back(m_DesktopItems[i]);
				}
				break;
			case DesktopItemType::File:
				{
					wstring ext = FilerFrogUtils::GetFileNameExtension(m_DesktopItems[i].filePath);

					if (FilerFrogUtils::ToLower(ext) == L"lnk" ||
						FilerFrogUtils::ToLower(ext) == L"url")
					{
						shortcutItems.push_back(m_DesktopItems[i]);
					}
					else
					{
						filesItems.push_back(m_DesktopItems[i]);
					}
				}
				break;
			}
		}

		std::sort(systemItems.begin(), systemItems.end(), DesktopItemSort());
		std::sort(folderItems.begin(), folderItems.end(), DesktopItemSort());
		std::sort(shortcutItems.begin(), shortcutItems.end(), DesktopItemSort());
		std::sort(filesItems.begin(), filesItems.end(), DesktopItemSort());


		RECT actualRect;
		
		ZeroMemory(&actualRect, sizeof(actualRect));

		APPBARDATA taskbarData;
		ZeroMemory(&taskbarData, sizeof(taskbarData));

		if (SHAppBarMessage(ABM_GETTASKBARPOS,&taskbarData) != 0)
		{
			actualRect = taskbarData.rc;
		}

		// width
		int cx = GetSystemMetrics(SM_CXSCREEN);
		// height
		int cy = GetSystemMetrics(SM_CYSCREEN);

		int virtual_desktop_width = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
		int virtual_desktop_height = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);

		Gdiplus::Bitmap bmp(virtual_desktop_width, virtual_desktop_height, PixelFormat24bppRGB);

		// Get the class identifier for the JPEG encoder.
		CLSID encoderClsid;
		GetEncoderClsid(L"image/bmp", &encoderClsid);

		Gdiplus::Graphics graphics(&bmp);

		DrawBackgroundImage(graphics, cx, cy, m_DesktopSortConfig.useExistingWallaper);

		PointF pos(0, 0);
		wstring title = L"";		

		int lastX = 0;
		int lastY = 64;

		int offsetX = 0;
		int offsetY = 0;

		offsetX = 64;
		offsetY = lastY;

		// Task bar is docked to top
		if (actualRect.right == cx && actualRect.bottom != cy)
		{
			offsetY += actualRect.bottom;
		}
		// Task bar is docked to left
		else if (actualRect.bottom == cy &&  actualRect.right != cx)
		{
			offsetX += actualRect.right;
		}

		DrawIconsGroup(L"System", offsetX, offsetY, graphics, systemItems, lastX, lastY);
		DrawIconsGroup(L"Folders", offsetX, offsetY, graphics, folderItems, lastX, lastY);
		DrawIconsGroup(L"Shortcuts", offsetX, offsetY, graphics, shortcutItems, lastX, lastY);
		DrawIconsGroup(L"Files", offsetX, offsetY, graphics, filesItems, lastX, lastY);

		wchar_t strPath[MAX_PATH];

		wchar_t tmpPath[MAX_PATH];
		GetTempPath(MAX_PATH, tmpPath);

		wstring tmpWallpaperBkp = (wstring)tmpPath + L"\\FilerFrogWallpaper.bmp";

		wstring desktopWallpaperPath = L"";
		bool frogDesktopExists = false;

		if (SHGetSpecialFolderPath(0, strPath, CSIDL_MYPICTURES,FALSE))
		{		
			desktopWallpaperPath = wstring(strPath) + L"\\FilerFrogWallpaper.bmp";

			if (PathFileExists(desktopWallpaperPath.c_str()))
			{
				frogDesktopExists = true;
			}

			ULONG quality = 100;
			EncoderParameters param;
			param.Count                       = 1;
			param.Parameter[0].Guid           = EncoderQuality;
			param.Parameter[0].Type           = EncoderParameterValueTypeLong;
			param.Parameter[0].NumberOfValues = 1;
			param.Parameter[0].Value          = &quality;

			CopyFile(desktopWallpaperPath.c_str(), tmpWallpaperBkp.c_str(), FALSE);

			bmp.Save(desktopWallpaperPath.c_str(), &encoderClsid, &param);

			SetWallpaperSettings(DesktopWallpaperStyle::BackgroundTile);

			SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (PVOID)desktopWallpaperPath.c_str(), SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
		}

		if (!m_SilentMode && FrogMessageBox(GetDesktopWindow(), L"Do you want to save changes?", L"FilerFrog - Sort Desktop", MB_YESNO) != IDYES)
		{
			if (frogDesktopExists)
			{
				CopyFile(tmpWallpaperBkp.c_str(), desktopWallpaperPath.c_str(), FALSE);
			}

			SetWallpaperSettings(wallpaperStyle);

			if (FilerFrogUtils::IsVistaOs() || FilerFrogUtils::ToLower(FilerFrogUtils::GetFileNameExtension(m_OriginalDesktop)) == L"bmp")
			{
				SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (PVOID)m_OriginalDesktop.c_str(), SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
			}
			else
			{				
				CoInitialize(NULL);

				HRESULT hr;
				IActiveDesktop* pActiveDesktop = NULL;

				//Create an instance of the Active Desktop
				hr = CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**)&pActiveDesktop);

				if(hr == S_OK && pActiveDesktop)
				{
					// change the wallpaper
					pActiveDesktop->SetWallpaper(m_OriginalDesktop.c_str(), 0);
					pActiveDesktop->ApplyChanges(AD_APPLY_ALL);

					// Call the Release method
					pActiveDesktop->Release();
				}

				CoUninitialize();
			}

			size_t itemCount = m_DesktopItems.size();

			LPCITEMIDLIST* itemsListArray = new LPCITEMIDLIST[itemsCount];						
			POINT* itemsPointArray = new POINT[itemsCount];

			for (int i=0; i<itemCount; i++)
			{				
				itemsListArray[i] = m_DesktopItems[i].itemIdList;

				itemsPointArray[i].x = m_DesktopItems[i].originalPosition.x;
				itemsPointArray[i].y = m_DesktopItems[i].originalPosition.y;
			}

			m_pFolderView->SelectAndPositionItems((UINT)itemsCount, itemsListArray, itemsPointArray, SVSI_NOSTATECHANGE);

			delete[] itemsListArray;
			delete[] itemsPointArray;

			if (explorerHwnd)
			{
				if (m_IsAutoArrange)
				{
					CheckAutoArrange(explorerHwnd);
				}

				if (m_IsSnapToGrid)
				{
					CheckSnapToGrid(explorerHwnd);
				}
			}
		}
		else
		{
			m_DesktopSortConfig.SaveToRegistry();
		}

		DeleteFile(tmpWallpaperBkp.c_str());

		return true;
	}
	else
	{
		return false;
	}	
}

wstring DesktopSorter::GetWallpaperPath()
{
	wstring imageFilePath = L"";

	if (m_DesktopSortConfig.originalWallpaperPath[0] == 0)
	{
		CRegKey key;
		
		long nError = key.Open(HKEY_CURRENT_USER, L"Control Panel\\Desktop", KEY_READ);

		if (nError == ERROR_SUCCESS)
		{
			// Code here to read values from MyProgram key
			wchar_t wallpapaerPath[MAX_PATH + 1];
			ULONG charCount = MAX_PATH + 1;

			nError = key.QueryStringValue(L"WallPaper", wallpapaerPath, &charCount);

			if (nError == ERROR_SUCCESS)
			{
				imageFilePath = wallpapaerPath;
				wcscpy_s(m_DesktopSortConfig.originalWallpaperPath, MAX_PATH, wallpapaerPath);
			}
		}
	}
	else
	{
		imageFilePath = m_DesktopSortConfig.originalWallpaperPath;
	}

	return imageFilePath;
}

void DesktopSorter::DrawBackgroundImage(Gdiplus::Graphics &graphics, int cx, int cy, bool useExistingWallpaper)
{
	if (useExistingWallpaper)
	{
		wstring imageFilePath = GetWallpaperPath();

		try
		{
			Gdiplus::Image img(imageFilePath.c_str());

			UINT width = img.GetWidth();
			UINT height = img.GetHeight();

			if ((int)width > cx)
			{
				width = cx;
			}

			if ((int)height > cx)
			{
				height = cy;
			}

			graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
			graphics.Clear(GetDesktopBackgroudColor());
			graphics.DrawImage(&img, Rect(0, 0, (UINT)cx, (UINT)cy), 0, 0, width, height, Gdiplus::Unit::UnitPixel);
		}
		catch (...)
		{
		}
	}
	else
	{
		graphics.Clear(GetDesktopBackgroudColor());
		Gdiplus::LinearGradientBrush gradientBrush(Gdiplus::PointF(0,0), Gdiplus::PointF((REAL)cx, (REAL)cy), m_DesktopSortConfig.bgGradientColor1, m_DesktopSortConfig.bgGradientColor2);
		graphics.FillRectangle(&gradientBrush, 0, 0, cx, cy);
	}
}

Gdiplus::Color DesktopSorter::GetDesktopBackgroudColor()
{
	Gdiplus::Color retVal(0,0,0);
	CRegKey key;

	long nError = key.Open(HKEY_CURRENT_USER, L"Control Panel\\Colors", KEY_READ);

	if (nError == ERROR_SUCCESS)
	{
		// Code here to read values from MyProgram key
		wchar_t sBackground[1024 + 1];
		ULONG charCount = 1024;

		nError = key.QueryStringValue(L"Background", sBackground, &charCount);

		wstring backgroundColor = sBackground;

		wstringstream ss(backgroundColor); // Insert the string into a stream
		wstring buff;

		wstring colors[3] = { L"0", L"0", L"0"};
		int i = 0;

		while (ss >> buff && i < 3)
		{
			colors[i] = buff;				
			i++;
		}

		BYTE r = _wtoi(colors[0].c_str());
		BYTE g = _wtoi(colors[1].c_str());
		BYTE b = _wtoi(colors[2].c_str());

		Gdiplus::Color solidColor(r, g, b);
		retVal = solidColor;
	}

	return retVal;
}

void DesktopSorter::DrawIconsGroup(const wstring& title, int& offsetX, int& offsetY, Gdiplus::Graphics& graphics, vector<DesktopItem>& desktopItems, int& lastX, int& lastY)
{
	if (desktopItems.size() > 0)
	{
		int fontHeight = (int)m_TextFont->GetHeight(&graphics);

		PointF pos;
		pos.X = (Gdiplus::REAL)(offsetX - 32);
		pos.Y = (Gdiplus::REAL)(offsetY - 64) - fontHeight + 28;

		PointF posShadow(pos);

		posShadow.Y = posShadow.Y + 2;
		posShadow.X = posShadow.X + 2;

		graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
		graphics.DrawString(title.c_str(), (INT)title.length(), m_TextFont , posShadow, &m_TextShadowBrush);
		graphics.DrawString(title.c_str(), (INT)title.length(), m_TextFont, pos, &m_TextBrush);

		PostionItems(desktopItems, offsetX, offsetY, lastX, lastY);

		INT x = offsetX - 32;
		INT y = offsetY - 32;
		INT w = 96 + lastX - offsetX;
		INT h = 112 + lastY - offsetY;

		Gdiplus::Color color1(m_DesktopSortConfig.groupOpacity,
			m_DesktopSortConfig.itemGradientColor1.GetR(),
			m_DesktopSortConfig.itemGradientColor1.GetG(),
			m_DesktopSortConfig.itemGradientColor1.GetB());

		Gdiplus::Color color2(m_DesktopSortConfig.groupOpacity,
			m_DesktopSortConfig.itemGradientColor2.GetR(),
			m_DesktopSortConfig.itemGradientColor2.GetG(),
			m_DesktopSortConfig.itemGradientColor2.GetB());

		Gdiplus::LinearGradientBrush gradientBrush(Gdiplus::PointF((REAL)x,0), Gdiplus::PointF((REAL)w + x, (REAL)0), color1, color2);	

		graphics.FillRectangle(&gradientBrush, x, y, w, h);
		graphics.DrawRectangle(&m_BroderPen, x, y, w, h);

		offsetY = lastY + 164;
	}
}

bool DesktopSorter::PostionItems(vector<DesktopItem>& items, int xOffset, int yOffset, int& lastX, int& lastY)
{
	size_t itemsCount = items.size();

	if (itemsCount > 0)
	{
		LPCITEMIDLIST* itemsListArray = new LPCITEMIDLIST[itemsCount];						
		POINT* itemsPointArray = new POINT[itemsCount];

		lastX = 0;
		lastY = 0;

		int iconSize = 64;
		int iconPadding = 16;
		int iconAreaSize = iconSize + iconPadding;

		// width
		int cx = GetSystemMetrics(SM_CXSCREEN) - xOffset;
		// height
		int cy = GetSystemMetrics(SM_CYSCREEN) - yOffset;

		int numOfAreas = (cx - 64 + 64) / iconAreaSize;

		for (int i=0; i<itemsCount; i++)
		{
			itemsListArray[i] = items[i].itemIdList;
			itemsPointArray[i].x = xOffset + ((i % numOfAreas) * (iconAreaSize));
			itemsPointArray[i].y = yOffset + ((i / numOfAreas) * (iconAreaSize));

			if (lastX < itemsPointArray[i].x)
			{
				lastX = itemsPointArray[i].x;
			}

			if (lastY < itemsPointArray[i].y)
			{
				lastY = itemsPointArray[i].y;
			}
		}

		m_pFolderView->SelectAndPositionItems((UINT)itemsCount, itemsListArray, itemsPointArray, SVSI_NOSTATECHANGE);

		delete[] itemsListArray;
		delete[] itemsPointArray;

		return true;
	}
	else
	{
		return false;
	}
}